﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using MoneyWentWhere.DataModel;
using System.Diagnostics;
using MoneyWentWhere.WinFormsUI.Properties;
using System.Collections;
using MoneyWentWhere.DataModel.Tx;

namespace MoneyWentWhere.WinFormsUI.UI.DataGrid
{
    public partial class TxSettingsPanel : UserControl,
        IAutoCompletePopup
    {
        // Temporary items while the panel is open

        Transaction _tx;
        TxSettings _settings;
        bool _useFilter = false;

        IAutoCompletePopupHost _host;

        // Caret tracking
        Timer _caretPositionTimer;
        int _caretPosition = -1;

        public TxSettingsPanel()
        {
            InitializeComponent();

            _caretPositionTimer = new Timer();
            _caretPositionTimer.Interval = 200;
            _caretPositionTimer.Tick += OnCaretPositionTimerTick;
        }

        void OnCaretPositionTimerTick(object sender, EventArgs e)
        {
            if (_host == null) { return; }
            if (_caretPosition == _host.SelectionStart) { return; }

            // Caret position changeds
            _caretPosition = _host.SelectionStart;
            UpdateTagsTab();
        }

        #region IAutoCompletePopup Members

        public void ShowPopup(IAutoCompletePopupHost host)
        {
            if (host == null) { throw new ArgumentNullException("host"); }
            if (_host == host) { return; }

            _host = host;
            _host.TextChanged += OnAutoCompleteTextChanged;

            // Initialize complete UI
            IDataGridViewEditingControl ec = _host as IDataGridViewEditingControl;
            if (ec == null) { return; }

            _tx = ec.EditingControlDataGridView.
                Rows[ec.EditingControlRowIndex].DataBoundItem as Transaction;

            if (_tx == null)
            {
                // In the ADDING NEW case
                BindingSource bs = ec.EditingControlDataGridView.DataSource as BindingSource;
                if (bs != null) { _tx = bs.Current as Transaction; }

                if (_tx == null) { throw new ArgumentException("No transaction exists."); }
            }

            _settings = TxSettings.Parse(_host.Text);
            _useFilter = false;

            UpdateTagsTab();
            UpdateTransferTab();
            UpdateGuiFromSettings();

            _caretPositionTimer.Start();
        }

        public void ClosePopup()
        {
            // BUGFIX: calling ClosePopup from within 
            // the panel itself must close the form.
            Form f = FindForm();
            if (f != null && f.Visible) { f.Hide(); }

            // Unsubscribe events
            if (_host != null)
            {
                _host.TextChanged -= OnAutoCompleteTextChanged;
            }

            _tx = null;
            _settings = null;
            _host = null;

            if (_caretPositionTimer != null)
            {
                _caretPositionTimer.Stop();
            }
        }

        public void OnHostKeyDown(KeyEventArgs e)
        {
            if (KeyChangesListSelection(e.KeyData))
            {
                ChangeListSelection(e.KeyData);
            }
        }

        void ChangeListSelection(Keys key)
        {
            ListBox listBox = null;
            if (tabs.SelectedTab == tabNormal) { listBox = lbTag; }
            else if (tabs.SelectedTab == tabTransfer) { listBox = lbToAccount; }

            if (listBox == null) { return; }

            if (key == Keys.Down &&
                listBox.SelectedIndex < listBox.Items.Count - 1)
            {
                listBox.SelectedIndex++;
            }
            else if (key == Keys.Up &&
                listBox.SelectedIndex > 0)
            {
                listBox.SelectedIndex--;
            }
        }

        static bool KeyChangesListSelection(Keys key)
        {
            return key == Keys.Down || key == Keys.Up;
        }

        public bool PopupWantsInputKey(Keys key)
        {
            return KeyChangesListSelection(key);
        }

        public void CommitValue()
        {
            _settings = CreateSettingsFromGui();
            _host.Text = _settings.ToRepString();
        }

        #endregion

        TxSettings CreateSettingsFromGui()
        {
            TransactionKind kind;

            if (tabs.SelectedTab == tabNormal) 
            {
                kind = cbReconcile.Checked ? TransactionKind.Reconcile : TransactionKind.Normal;
            }
            else if (tabs.SelectedTab == tabTransfer) { kind = TransactionKind.Transfer; }
            else { throw new InvalidOperationException("Unsupported tab: " + tabs.SelectedTab); }

            switch (kind)
            { 
                case TransactionKind.Normal:
                case TransactionKind.Reconcile:
                    // Splice in the selected tag
                    if (lbTag.SelectedItem != null)
                    {
                        String tagName = lbTag.SelectedItem.ToString();
                        tagName = NewItemGetItem(tagName); // Strip off new:'...'
                        ReplaceCurrentKeyword(tagName);
                    }
                    return NormalTxSettings.Parse(_host.Text);

                case TransactionKind.Transfer:
                    Account acct = lbToAccount.SelectedItem as Account;

                    if (ShouldShowTransferXRPanel())
                    {
                        return new TransferTxSettings(acct, ToAmount);
                    }
                    return new TransferTxSettings(acct);
            }
            return null;
        }

        void OnAutoCompleteTextChanged(object sender, EventArgs e)
        {
            _settings = TxSettings.Parse(_host.Text);
            _useFilter = true;

            // Prevent stealing of focus
            bool returnFocus = (this.ParentForm != null &&
                Form.ActiveForm != this.ParentForm);
            
            UpdateGuiFromSettings();

            if (returnFocus) { _host.ReturnFocus(); }
        }

        void UpdateGuiFromSettings()
        {
            TabPage tab = null;
            switch (_settings.Kind)
            {
                case TransactionKind.Normal:
                case TransactionKind.Reconcile:
                    tab = tabNormal;
                    UpdateTagsTab();
                    break;
                
                case TransactionKind.Transfer:
                    tab = tabTransfer;
                    UpdateTransferTab();
                    break;
            }

            if (tab != null && tabs.SelectedTab != tab) { tabs.SelectedTab = tab; }
        }

        String _tagFilter = null;
        void UpdateTagsTab()
        {
            NormalTxSettings settings = _settings as NormalTxSettings;
            if (settings == null) { settings = NormalTxSettings.Parse(""); }

            Transaction tx = _tx;
            if (tx == null) { return; }

            String keyword = GetCurrentKeyword().Trim();
            if (keyword == _tagFilter) { return; } // Items already filtered

            _tagFilter = keyword;
            FillTagAutoCompleteList(tx.DataSet.TagRegistry, _tagFilter);

            if (lbTag.Items.Count > 0) { lbTag.SelectedIndex = 0; }
        }

        const string NewPrefix = "new: '";
        const string NewSuffix = "'";

        static String NewItemEnclose(String item)
        {
            return NewPrefix + item + NewSuffix;
        }
        static String NewItemGetItem(String newItem)
        {
            if (newItem.StartsWith(NewPrefix) && newItem.EndsWith(NewSuffix))
            {
                newItem = newItem.Substring(NewPrefix.Length, newItem.Length - NewPrefix.Length - NewSuffix.Length);
            }
            return newItem;
        }

        void FillTagAutoCompleteList(TagRegistry allTags, String filter)
        {
            // Matching tags, most frequent first
            IEnumerable<Tag> tags = allTags;
            if (!String.IsNullOrEmpty(filter)) { tags = allTags.Where(x => x.Matches(filter)); } 
            tags = tags.OrderBy(x => -allTags.Count(x));

            // Add the tags
            lbTag.Items.Clear();
            lbTag.Items.AddRange(tags.ToArray());

            // Put exact match on top, or new item at the bottom
            String exactTagName = MoneyWentWhere.DataModel.Tag.GetValidTagName(filter);
            if (exactTagName != null)
            {
                Tag existingTag = tags.FirstOrDefault(x => x.Name.Equals(exactTagName, StringComparison.InvariantCultureIgnoreCase));
                if (existingTag != null)
                {
                    // Put it on top
                    lbTag.Items.Remove(existingTag);
                    lbTag.Items.Insert(0, existingTag);
                }
                else
                {
                    // Add a new item to the bottom
                    lbTag.Items.Add(NewItemEnclose(exactTagName));
                }
            }
        }


        String GetCurrentKeyword()
        {
            int start, end;
            GetCurrentKeywordBounds(out start, out end);
            String text = _host.Text;
            return text.Substring(start, end - start);
        }

        void ReplaceCurrentKeyword(String replacement)
        {
            int start, end;
            GetCurrentKeywordBounds(out start, out end);
            String text = _host.Text;
            String newText = text.Substring(0, start) + " " + replacement + " " + text.Substring(end);
            _host.Text = newText; 
        }

        void GetCurrentKeywordBounds(out int start, out int end)
        {
            if (_host == null) { throw new InvalidOperationException("_host is null"); }

            start = 0;
            if (_host.SelectionStart > 0)
            {
                start = _host.Text.LastIndexOfAny(TagSet.DelimiterChars, _host.SelectionStart - 1) + 1;
            }
            
            end = _host.Text.Length;
            if (_host.SelectionStart <= _host.Text.Length)
            {
                end = _host.Text.IndexOfAny(TagSet.DelimiterChars, _host.SelectionStart);
                if (end == -1) { end = _host.Text.Length; }
            }
        }

        void UpdateTransferTab()
        {
            TransferTxSettings settings = _settings as TransferTxSettings;
            if (settings == null) { settings = TransferTxSettings.Parse(""); }

            lbToAccount.SelectedIndex = -1;
            lbToAccount.Items.Clear();

            Transaction tx = _tx;
            if (tx == null) { return; }

            int selectedIndex = -1;
            settings.FillAutoComplete(lbToAccount.Items, out selectedIndex, tx, _useFilter);
            lbToAccount.SelectedIndex = selectedIndex;
            UpdateTransferInfoPanel();
        }

        #region TransferInfo

        void UpdateTransferInfoPanel()
        {
            TransferTxSettings settings = _settings as TransferTxSettings;
            if (settings == null) { settings = TransferTxSettings.Parse(""); }

            Account toAccount = lbToAccount.SelectedItem as Account;
            if (toAccount == null) { return; }

            bool toAcctCredited = true;
            if (_tx.Amount >= 0) { toAcctCredited = false; }
            lblTransferTitle.Text = (toAcctCredited ? "To" : "From") + " Account";

            bool showXR = SetTransferXRPanelVisible();
            if (showXR)
            {
                bnDirection.Image = toAcctCredited ? 
                    Resources.arrow_left : Resources.arrow_right;

                // From
                tbAmount.Text = Math.Abs(_tx.Amount).ToString("N2");
                lblCurrency.Text = _tx.Account.Currency.Id;

                // For the To amount, get from transferToTx or guess
                if (settings != null && settings.AmountSpecified)
                {
                    // Case 1: specified in settings
                    ToAmount = settings.Amount;
                }
                else if (_tx.TransferToTx != null && toAccount == _tx.TransferToTx.Account)
                {
                    // Case 2: specified in Tx (if it's the current one)
                    ToAmount = _tx.TransferToTx.Amount;
                }
                else
                {
                    // Case 3: determine XR from previous TX (if any)
                    decimal xr = _tx.DataSet.GetExchangeRate(_tx.Account.Currency, toAccount.Currency, _tx.Date);

                    if (xr <= 0)
                    {
                        // TODO: warn info doesn't exist
                        xr = 1;
                    }
                    ToAmount = -(xr * _tx.Amount);
                }
                lblToCurrency.Text = toAccount.Currency.Id;

                ExchangeRate = Currency.ExchangeRate(_tx.Amount, ToAmount);
                lblXRCurrencies.Text = toAccount.Currency + " = 1 " + _tx.Account.Currency;
            }
            else
            {
                lblCurrency.Text = "";
            }
        }

        decimal _exchangeRate;
        decimal ExchangeRate
        {
            get { return _exchangeRate; }
            set
            {
                _exchangeRate = value;

                // Invert for display (may switch to manual later)
                tbXR.TextChanged -= tbXR_TextChanged;                

                decimal xr = Math.Abs(_exchangeRate);
                tbXR.Text = xr.ToString("N2");
                if (xr < 1 && xr != 0)
                {
                    tbXR.Text = "1/" + (1/xr).ToString("N2");
                }
                tbXR.TextChanged += tbXR_TextChanged;
            }
        }

        private void tbXR_TextChanged(object sender, EventArgs e)
        {
            decimal xr;

            bool invert = false;
            String rep = tbXR.Text.Trim();
            if (rep.StartsWith("1/"))
            {
                rep = rep.Substring(2);
                invert = true;
            }

            if (decimal.TryParse(rep, out xr))
            {
                if (xr == 0) { return; }
                if (invert) { xr = 1 / xr; }

                _exchangeRate = xr;
                ToAmount = -(xr * _tx.Amount);
            }
        }

        decimal _toAmount;
        decimal ToAmount
        {
            get { return _toAmount; }
            set
            {
                _toAmount = value;

                tbToAmount.TextChanged -= tbToAmount_TextChanged;
                tbToAmount.Text = Math.Abs(ToAmount).ToString("N2");
                tbToAmount.TextChanged += tbToAmount_TextChanged;
            }
        }

        private void tbToAmount_TextChanged(object sender, EventArgs e)
        {
            decimal amount;
            if (decimal.TryParse(tbToAmount.Text, out amount) && amount != 0)
            {
                // TODO: consider sign
                _toAmount = amount;
                ExchangeRate = Currency.ExchangeRate(_tx.Amount, _toAmount);
            }
        }

        private void lbToAccount_SelectedIndexChanged(object sender, EventArgs e)
        {
            UpdateTransferInfoPanel();
        }

        bool ShouldShowTransferXRPanel()
        {
            bool visible = false;
            Account toAccount = lbToAccount.SelectedItem as Account;
            if (_tx != null && toAccount != null &&
                _tx.Amount != 0 &&
                _tx.Account.Currency != toAccount.Currency)
            {
                visible = true;
            }
            return visible;
        }

        bool SetTransferXRPanelVisible()
        {
            bool visible = ShouldShowTransferXRPanel();

            Trace.WriteLine("SetXRPanelVisible: " + visible);

            panelTransferXR.Visible = visible;
            if (visible)
            {
                lbToAccount.Height = tabTransfer.Height - lbToAccount.Top - panelTransferXR.Height;
            }
            else
            {
                lbToAccount.Height = tabTransfer.Height - lbToAccount.Top;
            }
            return visible;
        }

        #endregion

        private void lbToAccount_Click(object sender, EventArgs e)
        {
            if (!ShouldShowTransferXRPanel() && lbToAccount.SelectedItem != null)
            {
                CommitValue();
                ClosePopup();
            }
        }

        private void OnClick_CommitAndClose(object sender, EventArgs e)
        {
            CommitValue();
            ClosePopup();
        }

        private void OnEnterKeyUp_CommitAndClose(object sender, KeyEventArgs e)
        {
            if (e.KeyData == Keys.Enter)
            {
                CommitValue();
                ClosePopup();
            }
        }
    }
}
